﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Data;

namespace Xceed.Silverlight.Data
{
  internal static class DataUtils
  {
    #region Static Fields

    //The "1" is for convenience. It will always create the same Ids for the same calls.
    private static Random RandomGenerator = new Random( 1 );

    #endregion Static Fields

    internal static int GetUnit( int moveOrFetchSize )
    {
      return DataUtils.GetUnit( Convert.ToInt64( moveOrFetchSize ) );
    }

    internal static int GetUnit( long moveOrFetchSize )
    {
      return ( moveOrFetchSize > 0 ) ? 1 : -1;
    }

    internal static bool IsQueryingForward( long moveSize, int fetchSize )
    {
      if( moveSize == 0 )
        return ( fetchSize >= 0 );

      return ( moveSize >= 0 );
    }

    internal static ICollection ConvertToNonGeneric<T>( ICollection<T> collection )
    {
      if( collection == null )
        return null;

      ICollection newCollection = collection as ICollection;

      return ( newCollection != null )
               ? newCollection
               : new List<T>( collection );
    }

    internal static Guid CreateCallId()
    {
      byte[] guidBytes = new byte[ 16 ];

      for( int i = 0; i < 16; i++ )
      {
        guidBytes[ i ] = ( byte )DataUtils.RandomGenerator.Next(
                                   byte.MinValue,
                                   byte.MaxValue );
      }

      return new Guid( guidBytes );
    }

    internal static bool IsNullableType( this Type type )
    {
      if( type == null )
        return false;

      return ( type.IsClass )
          || ( type.IsNullableStruct() );
    }

    internal static bool IsNullableStruct( this Type type )
    {
      if( type == null )
        return false;

      return ( type.IsGenericType )
          && ( type.GetGenericTypeDefinition() == typeof( Nullable<> ) );
    }

    internal static bool IsMethodImplemented( MethodInfo methodInfo, Type derivedType )
    {
      if( ( methodInfo == null ) || ( derivedType == null ) )
        return false;

      MethodInfo overridedMethodInfo = DataUtils.GetOverridedMethodDefinition( methodInfo, derivedType );
      if( ( overridedMethodInfo == null ) || ( overridedMethodInfo.IsAbstract ) )
        return false;

      return ( methodInfo.DeclaringType != overridedMethodInfo.DeclaringType );
    }

    internal static MethodInfo GetMethodDefinition<T1, TResult>( Expression<Func<T1, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static MethodInfo GetMethodDefinition<T1, T2, TResult>( Expression<Func<T1, T2, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static MethodInfo GetMethodDefinition<T1, T2, T3, TResult>( Expression<Func<T1, T2, T3, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static MethodInfo GetMethodDefinition<T1, T2, T3, T4, TResult>( Expression<Func<T1, T2, T3, T4, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static MethodInfo GetMethodDefinition<T1, T2, T3, T4, T5, TResult>( Expression<Func<T1, T2, T3, T4, T5, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static MethodInfo GetMethodDefinition<T1, T2, T3, T4, T5, T6, TResult>( Expression<Func<T1, T2, T3, T4, T5, T6, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static RelativeOffset CalculateRelativeOffset(
      RelativeOffset currentOffset,
      long absoluteOffsetDifference )
    {
      return DataUtils.CalculateRelativeOffset( currentOffset, absoluteOffsetDifference, true );
    }

    internal static RelativeOffset CalculateRelativeOffset(
      RelativeOffset currentOffset,
      long absoluteOffsetDifference,
      bool maxToZero )
    {
      RelativeOffset result = null;

      if( currentOffset != null )
      {
        if( currentOffset.ReferencePosition == ReferencePoint.BeforeFirst )
        {
          result = currentOffset + absoluteOffsetDifference;
        }
        else
        {
          result = currentOffset - absoluteOffsetDifference;
        }

        if( maxToZero && result.Value < 0 )
        {
          EstimateInt64 newOffset = new EstimateInt64( 0, true );

          result = new RelativeOffset( newOffset, result.ReferencePosition );
        }
      }

      return result;
    }

    internal static IEnumerable<string> GetGroupNames( IEnumerable<GroupDescription> groupDescriptions )
    {
      foreach( GroupDescription groupDescription in groupDescriptions )
      {
        yield return DataUtils.GetGroupName( groupDescription );
      }
    }

    internal static IEnumerable<SortDescription> GetSortOrder(
      IEnumerable<string> groupNames,
      IEnumerable<SortDescription> sortOrder,
      IEnumerable<SortDescription> primaryKeys )
    {
      List<SortDescription> result = new List<SortDescription>();
      HashSet<string> groupedProperties = new HashSet<string>( groupNames.Distinct() );
      ILookup<string, SortDescription> sortedProperties = sortOrder.ToLookup( ( item ) => item.PropertyName );
      ILookup<string, SortDescription> primaryKeyProperties = primaryKeys.ToLookup( ( item ) => item.PropertyName );

      // Add the current grouping to the sort order.
      foreach( string groupName in groupNames )
      {
        if( sortedProperties.Contains( groupName ) )
        {
          result.Add( sortedProperties[ groupName ].Single() );
        }
        else if( primaryKeyProperties.Contains( groupName ) )
        {
          result.Add( primaryKeyProperties[ groupName ].Single() );
        }
        else
        {
          result.Add( new SortDescription( groupName, ListSortDirection.Ascending ) );
        }
      }

      // Add the current sorting to the sort order
      foreach( SortDescription sortDescription in sortOrder )
      {
        if( !groupedProperties.Contains( sortDescription.PropertyName ) )
        {
          result.Add( sortDescription );
        }
      }

      // Add the primary keys to the sort order
      foreach( SortDescription sortDescription in primaryKeys )
      {
        string propertyName = sortDescription.PropertyName;

        if( ( !groupedProperties.Contains( propertyName ) )
          && ( !sortedProperties.Contains( propertyName ) ) )
        {
          result.Add( sortDescription );
        }
      }

      return result.AsReadOnly();
    }

    public static bool ResolvePosition<T>(
      SourcePosition sourcePosition,
      bool considerRealPosition,
      out SourcePosition resolvedSourcePosition,
      out T resolvedItem,
      out long resolvedOffset,
      out bool approximateOffset,
      out Func<T, SourcePosition> positionResolver )
    {
      if( sourcePosition == null )
        throw new ArgumentNullException( "sourcePosition" );

      bool isBasedOnItem;

      if( sourcePosition is DummyPosition<T> )
      {
        DummyPosition<T> dummyPosition = ( DummyPosition<T> )sourcePosition;
        if( considerRealPosition && dummyPosition.RealPosition != null )
        {
          isBasedOnItem = DataUtils.ResolvePosition(
            dummyPosition.RealPosition,
            considerRealPosition,
            out resolvedSourcePosition,
            out resolvedItem,
            out resolvedOffset,
            out approximateOffset,
            out positionResolver );
        }
        else
        {
          if( dummyPosition.BasedOnItem )
          {
            positionResolver = null;
            isBasedOnItem = true;
            resolvedSourcePosition = null;
            resolvedItem = dummyPosition.BaseItem;
            resolvedOffset = dummyPosition.BaseOffset;
            approximateOffset = dummyPosition.BaseApproximateOffset;
          }
          else
          {
            long subOffset;
            bool subApproximateOffset;

            isBasedOnItem = DataUtils.ResolvePosition(
              dummyPosition.BaseSourcePosition,
              considerRealPosition,
              out resolvedSourcePosition,
              out resolvedItem,
              out subOffset,
              out subApproximateOffset,
              out positionResolver );

            resolvedOffset = dummyPosition.BaseOffset + subOffset;
            approximateOffset = dummyPosition.BaseApproximateOffset || subApproximateOffset;
          }
        }
      }
      else if( sourcePosition is DependentPosition<T> )
      {
        DependentPosition<T> dependentPosition = ( DependentPosition<T> )sourcePosition;
        isBasedOnItem = true;
        resolvedItem = dependentPosition.DependentItem;
        resolvedOffset = 0;
        resolvedSourcePosition = null;
        approximateOffset = false;
        positionResolver = dependentPosition.PositionResolver;
      }
      else
      {
        resolvedSourcePosition = sourcePosition;
        positionResolver = null;
        resolvedOffset = 0;
        approximateOffset = false;

        if( sourcePosition is ItemPosition )
        {
          resolvedItem = ( T )( ( ItemPosition )sourcePosition ).Item;
          isBasedOnItem = true;
        }
        else
        {
          resolvedItem = default( T );
          isBasedOnItem = false;
        }
      }

      return isBasedOnItem;
    }

    private static MethodInfo GetOverridedMethodDefinition( MethodInfo methodInfo, Type derivedType )
    {
      MethodInfo overridedMethodInfo = null;

      if( ( methodInfo != null )
        && ( methodInfo.IsVirtual || methodInfo.IsAbstract )
        && ( derivedType != null ) )
      {
        string methodName = methodInfo.Name;
        BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
        Type[] parameterTypes = ( from parameter in methodInfo.GetParameters()
                                  select parameter.ParameterType ).ToArray();

        overridedMethodInfo = derivedType.GetMethod(
                                methodName,
                                bindingFlags,
                                null,
                                parameterTypes,
                                null );
      }

      return overridedMethodInfo;
    }

    private static MethodInfo GetMethodDefinition( Expression lambda )
    {
      LambdaExpression expression = lambda as LambdaExpression;

      if( expression != null )
      {
        MethodCallExpression methodCall = expression.Body as MethodCallExpression;

        if( methodCall != null )
          return methodCall.Method;
      }

      return null;
    }

    private static string GetGroupName( GroupDescription groupDescription )
    {
      PropertyGroupDescription propertyGroupDescription = groupDescription as PropertyGroupDescription;
      if( propertyGroupDescription != null )
        return propertyGroupDescription.PropertyName;

      DataGroupDescription dataGroupDescription = groupDescription as DataGroupDescription;
      if( dataGroupDescription != null )
        return dataGroupDescription.PropertyName;

      throw new ArgumentException( "The specified groupDescription must be a PropertyGroupDescription or DataGroupDescription instance.", "groupDescription" );
    }
  }
}
